////////////////////////////////////////////////////////////////////////////////

#ifndef _CLIENTGUI_H_
#include "clientgui.h"
#endif

////////////////////////////////////////////////////////////////////////////////
       
#include <iostream>
#include <cassert>

#ifndef _CLIENT_H_
#include "client.h"
#endif

#ifndef _SERVERSTATEPACKET_H_
#include "serverstatepacket.h"
#endif

#ifndef _CLIENTINPUTPACKETONCLIENT_H_
#include "clientinputpacketonclient.h"
#endif

////////////////////////////////////////////////////////////////////////////////


ClientGUI::ClientGUI()
: m_Player(NULL),
  m_Bullet(NULL),
  m_Background(NULL),
  m_Client(NULL),
  m_Stop(false)
{
    AddEventToObserve(Observer::KEY_INPUT_CHANGE);
    AddEventToObserve(Observer::MOUSE_POSITION_CHANGE);

    m_Input.AddObserver(this);
}


////////////////////////////////////////////////////////////////////////////////


ClientGUI::~ClientGUI()
{
    m_Input.RemoveObserver(this);
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::Start( Client* _client )
{
    m_Client = _client;
    LoadImages();
    RunGameLoop();
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::LoadImages()
{
    // Load player image
    m_Player = m_Graphics.LoadObject("rc/player.png");

    // Load bullet image
    m_Bullet = m_Graphics.LoadObject("rc/bullet.png");

    // Load background image
    m_Background = m_Graphics.LoadObject("rc/background.png");

    if ( !m_Player || !m_Bullet || !m_Background )
        std::cout << "FAIL LOADING IMAGES" << std::endl;
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::Render()
{
    // Lock render queue for adding new states from server for a while
    m_RenderQueueMutex.Lock();

    // Fetch pointers
    NextBattlefieldState nextState = GetNextStateFromQueue();

    // If two states taken successfully
    if (nextState.first)
    {
        // Integrate and get intermediate state
        m_Battlefield.Integrate(*(nextState.first), nextState.second);

        // Unlock queue before rendering, because we have already copied info
        // to intermediate state and we do not need queue any more.
        // And because rendering can take a lot of time.
        m_RenderQueueMutex.Unlock();

        // Render intermediate state
        RenderBattlefield();
    }
    else
    {
        // Unlock queue
        m_RenderQueueMutex.Unlock();
    }
}


////////////////////////////////////////////////////////////////////////////////


ClientGUI::NextBattlefieldState ClientGUI::GetNextStateFromQueue()
{
    NextBattlefieldState nextState(NULL, 0.0);

    // Number of packets with state to render
    size_t queueSize = m_RenderQueue.size();

    // Render speed multiplier
    double speedMultiplier = 1.0;

    if ( queueSize < 2 )
    {
        // Less than 2 packets in queue - it's not enough to render
        return nextState;
    }

    // Normal render speed is when there is 5 packets in queue
    speedMultiplier = queueSize * 0.2;

    // Previous render time
    static size_t prevTime = Timer::GetCurrTime();

    // Current render time
    size_t currTime = Timer::GetCurrTime();

    // Delta time from previous render
    size_t timeFromPrevRender = static_cast<size_t>((currTime - prevTime) * speedMultiplier );

    // Time of first and second state
    const size_t& firstServerTime = m_RenderQueue[0].first;
    const size_t& secondServerTime = m_RenderQueue[1].first;

    if (firstServerTime + timeFromPrevRender <= secondServerTime)
    {
        // We are still between these two states. Integrate

        // Next state
        nextState.first = &m_RenderQueue[1].second;

        // Alpha
        if ( secondServerTime ==  prevTime )
            nextState.second = 0;
        else
            nextState.second = (double)timeFromPrevRender / (double)(secondServerTime - prevTime);

        // Update time
        prevTime = currTime;

        return nextState;
    }
    
    // We are not between these states. Jump to two new states.
    m_RenderQueue.pop_front();

    // Update time
    prevTime = currTime;

    // And render
    return GetNextStateFromQueue();
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::RenderBattlefield()
{
    // Ensure images are loaded
    if ( !m_Player || !m_Bullet || !m_Background )
        return;

    // Draw background
    m_Graphics.DrawObject(m_Background, 0, 0);

    // Get players
    const PlayersList& players = m_Battlefield.GetPlayers();
    for (PlayersList::const_iterator it = players.begin();
        it != players.end(); ++it)
    {
        const Player& player = *it;
        int xPos = static_cast<int>(player.GetPosition().x);
        int yPos = static_cast<int>(player.GetPosition().y);

        m_Graphics.DrawObject(m_Player, xPos, yPos);
    }

    // Get bullets
    const BulletsList& bullets = m_Battlefield.GetBullets();
    for (BulletsList::const_iterator it = bullets.begin();
        it != bullets.end(); ++it)
    {
        const Bullet& bullet = *it;
        int xPos = static_cast<int>(bullet.GetPosition().x);
        int yPos = static_cast<int>(bullet.GetPosition().y);

        m_Graphics.DrawObject(m_Bullet, xPos, yPos);
    }

    // Get walls
    const WallsList& walls = m_Battlefield.GetWalls();
    for (WallsList::const_iterator it = walls.begin();
        it != walls.end(); ++it)
    {
        const Wall& wall = *it;
        int xPos = static_cast<int>(wall.GetPosition().x);
        int yPos = static_cast<int>(wall.GetPosition().y);

        m_Graphics.DrawObject(m_Player, xPos, yPos);
    }

    // Update screen
    m_Graphics.Update();
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::AddToRenderQueue( const ServerStatePacket& _packet )
{
    static size_t lastPacketTime = _packet.GetCreationTime();
    if (_packet.GetCreationTime() >= lastPacketTime)
    {
        lastPacketTime = _packet.GetCreationTime();

        m_RenderQueueMutex.Lock();
        m_RenderQueue.push_back( std::make_pair(_packet.GetCreationTime(), _packet.GetState() ) );
        m_RenderQueueMutex.Unlock();
    }
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::RunGameLoop()
{
    while (!m_Stop)
    {
        // Process input
        ProcessInput();

        // Render state
        Render();

        // Delay
        Timer::Sleep(10);
    }
}


////////////////////////////////////////////////////////////////////////////////


const ClientInput& ClientGUI::GetClientInput() const
{
    return m_ClientInput;
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::OnEvent( Event _event, Observable* _observable )
{
    switch (_event)
    {
    case Observer::KEY_INPUT_CHANGE:
        OnKeyChanged();
        break;

    case Observer::MOUSE_POSITION_CHANGE:
        OnMousePositionChanged();   
        break;

    default:
        assert(0);
        break;
    }
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::OnKeyChanged()
{
    Input::Key key = m_Input.GetKey();
    switch (key.m_Code)
    {
    case Input::W:
        m_ClientInput.SetUpPressed(key.m_State == Input::PRESSED);
        break;
    case Input::S:
        m_ClientInput.SetDownPressed(key.m_State == Input::PRESSED);
        break;
    case Input::A:
        m_ClientInput.SetLeftPressed(key.m_State == Input::PRESSED);
        break;
    case Input::D:
        m_ClientInput.SetRightPressed(key.m_State == Input::PRESSED);
        break;
    case Input::MOUSE1:
        m_ClientInput.SetShootPressed(key.m_State == Input::PRESSED);
        break;
    case Input::EXIT:
        m_Stop = true;
        break;

    default:
        assert(0);
        break;
    }
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::OnMousePositionChanged()
{
    Input::MousePosition pos = m_Input.GetMousePosition();
    m_ClientInput.SetMousePosition( PE::Point( static_cast<double>(pos.x),
                                               static_cast<double>(pos.y) ) );
}


////////////////////////////////////////////////////////////////////////////////


void ClientGUI::ProcessInput()
{
    // Save previous input
    ClientInput prevInput = m_ClientInput;

    // Process input
    m_Input.ProcessInput();

    // Input changed?
    if ( m_ClientInput.InputChanged(prevInput) )
    {
        ClientInputPacket packet(Timer::GetCurrTime(), m_ClientInput);
        ClientInputPacketOnClient packetOnClient(packet);
        m_Client->Send(&packetOnClient);
    }
}


////////////////////////////////////////////////////////////////////////////////
